package com.ist.neartweat.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Random;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

import com.ist.neartweat.messages.Message;
import com.ist.neartweat.messages.NewPollMessage;
import com.ist.neartweat.messages.NewTweatUpdateMessage;
import com.ist.neartweat.messages.PollListMessage;
import com.ist.neartweat.messages.PollResultMessage;
import com.ist.neartweat.messages.RegistrationMessage;
import com.ist.neartweat.messages.ReplyMessage;
import com.ist.neartweat.platForm.Reply;

public class NetworkService extends Service {
	// Binder given to clients
	private final IBinder mBinder = new NetBinder();
	// Random number generator
	private final Random mGenerator = new Random();
	private Socket sckt ;
	private ObjectOutputStream ops;
	private ObjectInputStream ois ;
	private boolean readMsgs= true;
	ConnectThread ctr ;
	private ArrayList<Message> msgs = new ArrayList<Message>();
	private ArrayList<Message> pollMsgs = new ArrayList<Message>();
	private PollListMessage pollListMsg = new PollListMessage();//This is set when the service receives PollListMessage after RegistrationSuccessMessage.
	private Hashtable<String,ArrayList<ReplyMessage>> replyMsgs = new Hashtable<String,ArrayList<ReplyMessage>>();
	int msgReadCnt =0;
	/**
	 * Class used for the client Binder.  Because we know this service always
	 * runs in the same process as its clients, we don't need to deal with IPC.
	 */
	public class NetBinder extends Binder {
		public NetworkService getService() {
			// Return this instance of LocalService so clients can call public methods
			return NetworkService.this;
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	/** method for clients 
	 * @throws IOException */
	public synchronized void sendMsg(Message msg) throws IOException{
		if(msg instanceof ReplyMessage){

			ReplyMessage rm = (ReplyMessage)msg;
			msgs.add(msg);
			rm.setClientReceptionTime(System.currentTimeMillis());
			if(replyMsgs.containsKey(rm.getTweatId())){
				replyMsgs.get(rm.getTweatId()).add(rm);
			}
			else{
				ArrayList<ReplyMessage> r = new ArrayList<ReplyMessage>();
				r.add(rm);
				replyMsgs.put(rm.getTweatId(),r );
			}
		}
		ops.writeObject(msg);
	}

	/** method for clients */
	public ArrayList<Message> giveMsgs(){
		if(msgReadCnt == msgs.size())
			return null;
		else{
			synchronized(msgs){//Synchronized because another thread in Connect method continuously populates the messages.
				ArrayList<Message>  al = new ArrayList<Message>(msgs.subList(msgReadCnt, msgs.size()));
				msgReadCnt = msgs.size();
				return al;
			}

		}

	}

	public boolean connect(String ip, int port,String clientId) {

		ctr = new ConnectThread(ip,port,sckt,ops,ois);
		ctr.start();
		long cTime = System.currentTimeMillis();
		while(System.currentTimeMillis() - cTime <10000){
			/*
			 * wait for 10 seconds for the connection.
			 */
		}
		if(ctr.connected){
			try {
				sckt = ctr.getSocket();
				ops = ctr.getOps();
				ois = ctr.getIps();
				RegistrationMessage reg = new RegistrationMessage(clientId);
				ops.writeObject(reg);
				System.out.println("From Service ::Successfully Connected");

				new Thread(){
					public void run(){
						while(readMsgs){
							try {
								Message msg = (Message)ois.readObject();

								addMsg(msg);


							} catch (OptionalDataException e) {
								System.out.println("Except1");
								// TODO Auto-generated catch block
								e.printStackTrace();
							} catch (ClassNotFoundException e) {
								// TODO Auto-generated catch block
								System.out.println("Except1");
								e.printStackTrace();
							} catch (IOException e) {
								// TODO Auto-generated catch block
								System.out.println("Except1");
								e.printStackTrace();
							}
						}
					}

				}.start();

				return true;
			}
			catch (UnknownHostException e) {
				System.out.println("Exception while connecting");
				e.printStackTrace();
				return false;
			} catch (IOException e) {
				System.out.println("Exception while connecting");
				e.printStackTrace();
				return false;
			}
		}
		else
			return false;
	} 

	private void addMsg(Message msg){
		
		if(msg instanceof PollListMessage){
			this.pollListMsg = (PollListMessage)msg;
			return;
		}
		
		else if(msg instanceof NewPollMessage){
			this.pollMsgs.add(msg);
			return;
		}
		
		else if(msg instanceof PollResultMessage){
			this.pollMsgs.add(msg);
			return;
		}
		else if(msg instanceof ReplyMessage){

			ReplyMessage rm = (ReplyMessage)msg;
			synchronized(msgs){
				msgs.add(msg);
			}
			rm.setClientReceptionTime(System.currentTimeMillis());
			
			if(replyMsgs.containsKey(rm.getTweatId())){
				replyMsgs.get(rm.getTweatId()).add(rm);
			}
			else{
				ArrayList<ReplyMessage> r = new ArrayList<ReplyMessage>();
				r.add(rm);
				replyMsgs.put(rm.getTweatId(),r );
			}
			return;
		}
		else if(msg instanceof NewTweatUpdateMessage){
			synchronized(msgs){
				msgs.add(msg);
			}
			return;
		}
	}
	
	public ArrayList<Message> givePollMsgs(){
		return this.pollMsgs;
	}

	public ArrayList<ReplyMessage> giveReplyMsgs(String tweatId, long timeStamp) {
		if(replyMsgs.containsKey(tweatId)){
			ArrayList<ReplyMessage> r = replyMsgs.get(tweatId);
			ArrayList<ReplyMessage> toSend = new ArrayList<ReplyMessage>();
			for(ReplyMessage re: r){
				if(re.getClientReceptionTime()>timeStamp){
					toSend.add(re);
				}
			}
			return toSend;
		}
		else{
			return null;
		}
	}

	public PollListMessage getPollListMsg() {
		return pollListMsg;
	}

	public void setPollListMsg(PollListMessage pollListMsg) {
		this.pollListMsg = pollListMsg;
	}
}
class ReplyWrapper{
	long receptionTime ;
	Reply reply;
	ReplyWrapper(long rtime, Reply r){
		this.receptionTime = rtime;
		this.reply = r;
	}

}

class ConnectThread extends Thread{

	String ip;
	int port;
	Socket sckt;
	boolean connected ;
	ObjectOutputStream ops;
	ObjectInputStream ois;
	ConnectThread(String ip,int port,Socket sckt,ObjectOutputStream ops,ObjectInputStream ois){
		this.sckt =sckt;
		this.ip = ip;
		this.port = port;
		this.ops = ops;
		this.ois = ois;
	}
	public void run(){

		try {
			sckt = new Socket(ip,port);
			InputStream iis = sckt.getInputStream();
			OutputStream oos = sckt.getOutputStream();
			ops = new ObjectOutputStream(oos);
			ops.flush();
			ois =  new ObjectInputStream(iis);
			connected = true;
		} catch (UnknownHostException e) {
			connected = false;
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			connected = false;
			e.printStackTrace();
		}

	}
	Socket getSocket(){
		return sckt;
	}
	ObjectOutputStream getOps(){
		return ops;
	}
	ObjectInputStream getIps(){
		return ois;
	}


}


